package com.example.teesystem.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.teesystem.common.utils.IssueUtil;
import com.example.teesystem.common.utils.PdfFontUtil;
import com.example.teesystem.common.utils.Response;
import com.example.teesystem.common.utils.ResultCode;
import com.example.teesystem.entity.*;
import com.example.teesystem.entity.vo.IssueRecord.IssueRecordInsertVo;
import com.example.teesystem.entity.vo.exam.ExamInsertVo;
import com.example.teesystem.entityDto.ExamPaperSimpleMessage;
import com.example.teesystem.entityDto.ExaminationIssueList;
import com.example.teesystem.entityDto.IssueBriefMessage;
import com.example.teesystem.filter.MyHandlerInterceptor;
import com.example.teesystem.mapper.*;
import com.example.teesystem.service.ExamService;
import com.example.teesystem.service.IssueRecordService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletResponse;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * @author NieYaShi
 * @description 针对表【t_exam】的数据库操作Service实现
 * @createDate 2022-04-15 14:57:54
 */
@Service
@RequiredArgsConstructor
public class ExamServiceImpl extends ServiceImpl<ExamMapper, Exam>
        implements ExamService {

    private final HttpServletResponse response;

    private final ExamPaperMapper examPaperMapper;
    private final ExamIssueMapper examIssueMapper;
    private final ExamMapper examMapper;
    private final ExamEvaluationMapper examEvaluationMapper;
    private final IssueMapper issueMapper;
    private final ClassStudentMapper classStudentMapper;

    private final IssueRecordService issueRecordService;

    private static final Map<Integer, Map<Integer, List<IssueRecordInsertVo>>> tempCache = new ConcurrentHashMap<>();

    private static final int ERROR = -1;

    @PostConstruct
    private void initCache(){
        Date date = new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
        try {
            Date curTime = simpleDateFormat.parse(simpleDateFormat.format(date));

            QueryWrapper<ExamPaper> queryWrapper = new QueryWrapper<>();
            queryWrapper.lambda()
                    .eq(ExamPaper::getIsPublish, 1)
                    .eq(ExamPaper::getIsFinish, 0)
                    .eq(ExamPaper::getIsMark, 0)
                    .le(ExamPaper::getStartTime, curTime)
                    .ge(ExamPaper::getEndTime, curTime);

            List<ExamPaper> examPaperList = examPaperMapper.selectList(queryWrapper);

            Map<Integer, List<Integer>> classStudentsMap = new HashMap<>();

            for (ExamPaper examPaper : examPaperList){
                String classes = examPaper.getClasses();
                classes = classes.trim();
                if (!classes.equals("")){
                    String[] classIds = classes.split("\\$");

                    Map<Integer, List<IssueRecordInsertVo>> studentMap = new HashMap<>();
                    for (String classId : classIds){
                        List<Integer> studentIds;
                        if (classStudentsMap.get(Integer.parseInt(classId)) == null) {
                            studentIds = classStudentMapper.getStudentIdByClassId(Integer.parseInt(classId));
                            if (studentIds.size() == 0) {
                                continue;
                            }
                            classStudentsMap.put(Integer.parseInt(classId), studentIds);
                        }else {
                            studentIds = classStudentsMap.get(Integer.parseInt(classId));
                        }

                        for (Integer studentId : studentIds){
                            List<Exam> examList = examMapper.getExamsByPaperIdAndStudentId(examPaper.getId(), studentId);
                            if (examList.size() == 0){
                                continue;
                            }
                            List<IssueRecordInsertVo> recordInsertVoList = new ArrayList<>();
                            for (Exam exam : examList){
                                recordInsertVoList.add(new IssueRecordInsertVo(exam));
                            }
                            studentMap.put(studentId, recordInsertVoList);
                        }
                    }
                    tempCache.put(examPaper.getId(), studentMap);
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    @Override
    @Transactional
    public Response addExamAnswer(ExamInsertVo examInsertVo) {
        ExamPaper check = examPaperMapper.selectById(examInsertVo.getPaperId());
        //试卷验证
        if (check == null){
            return Response.error(ResultCode.EXAM_EXIST_ERROR);
        }
        if (check.getIsPublish() == 0){
            return Response.error(ResultCode.EXAM_NOT_PUBLISH_ERROR);
        }
        if (check.getIsFinish() == 1){
            return Response.error(ResultCode.EXAM_TIMEOUT_ERROR);
        }
        if (check.getIsMark() == 1){
            return Response.error(ResultCode.GET_ERROR);
        }

        //试题验证
        List<Integer> examIssueIdList = examIssueMapper.getIssueListByPaperId(examInsertVo.getPaperId());
        if (examIssueIdList.size() == 0){
            return Response.error(ResultCode.EXAM_NOT_HAVE_ISSUE_ERROR);
        }

        //时间验证
        try{
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
            Date startDate = sdf.parse(sdf.format(new Date(check.getStartTime().getTime() - 5 * 60 * 1000)));
            Date endDate = sdf.parse(sdf.format(new Date(check.getEndTime().getTime() + 2 * 60 * 1000)));
            Date currentDate = sdf.parse(sdf.format(new Date()));
            if (currentDate.compareTo(startDate) < 0){
                return Response.error(ResultCode.EXAM_NOT_STARTED_ERROR);
            }
            if (currentDate.compareTo(endDate) > 0){
                //未进入考试学生插入答题记录
                failToEnterExamStudent(check, examIssueIdList);
                //防止提交后再次进入答题
                addExamEvaluation(check);
                //修改试卷状态
                check.setIsFinish(1);
                examPaperMapper.updateById(check);
                //清理缓存
                tempCache.remove(examInsertVo.getPaperId());
                return Response.error(ResultCode.EXAM_TIMEOUT_ERROR);
            }
        }catch (Exception e){
            return Response.error(ResultCode.DATE_FORMAT_ERROR.getMsg(), ResultCode.DATE_FORMAT_ERROR.getCode());
        }

        Map<Integer, List<IssueRecordInsertVo>> studentCache = tempCache.get(examInsertVo.getPaperId());
        if (studentCache == null) {
            studentCache = new HashMap<>();
        }

        List<IssueRecordInsertVo> preCache = studentCache.get(MyHandlerInterceptor.getUid());
        for (IssueRecordInsertVo studentAnswer : examInsertVo.getIssueAnswerVoList()) {
            //更新答题记录
            issueRecordService.addIssueRecordService(studentAnswer);

            if (!examIssueIdList.contains(studentAnswer.getIssueId())) {
                return Response.error(ResultCode.ISSUE_NOT_EXIST_ERROR);
            }

            int index = isContain(preCache, studentAnswer);
            if (preCache != null && index != ERROR) {
                Exam exam = examMapper.getExamByPaperIssueAndStudentId(examInsertVo.getPaperId(), studentAnswer.getIssueId(), MyHandlerInterceptor.getUid());
                exam.setAnswer(studentAnswer.getAnswer().toUpperCase());
                examMapper.updateById(exam);
                //更新缓存
                preCache.set(index, studentAnswer);
            } else {
                Exam exam = new Exam();
                BeanUtils.copyProperties(studentAnswer, exam);
                exam.setPaperId(examInsertVo.getPaperId());
                exam.setStudentId(MyHandlerInterceptor.getUid());

                int id = ERROR;
                examMapper.insert(exam);
                id = exam.getId();
                if (id == ERROR) {
                    return Response.error(ResultCode.INSERT_ERROR);
                }
            }
        }

        studentCache.put(MyHandlerInterceptor.getUid(), examInsertVo.getIssueAnswerVoList());
        tempCache.put(examInsertVo.getPaperId(), studentCache);

        return Response.ok();
    }

    @Override
    public synchronized Response submitExamAnswer(ExamInsertVo examInsertVo) {
        ExamPaper check = examPaperMapper.selectById(examInsertVo.getPaperId());
        //试卷验证
        if (check == null){
            return Response.error(ResultCode.EXAM_EXIST_ERROR);
        }
        if (check.getIsPublish() == 0){
            return Response.error(ResultCode.EXAM_NOT_PUBLISH_ERROR);
        }
        if (check.getIsFinish() == 1){
            return Response.error(ResultCode.EXAM_TIMEOUT_ERROR);
        }
        if (check.getIsMark() == 1){
            return Response.error(ResultCode.GET_ERROR);
        }

        //试题验证
        List<Integer> examIssueIdList = examIssueMapper.getIssueListByPaperId(examInsertVo.getPaperId());
        if (examIssueIdList.size() == 0){
            return Response.error(ResultCode.EXAM_NOT_HAVE_ISSUE_ERROR);
        }

        //时间验证
        try{
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
            Date startDate = sdf.parse(sdf.format(new Date(check.getStartTime().getTime() - 5 * 60 * 1000)));
            Date endDate = sdf.parse(sdf.format(new Date(check.getEndTime().getTime() + 2 * 60 * 1000)));
            Date currentDate = sdf.parse(sdf.format(new Date()));
            if (currentDate.compareTo(startDate) < 0){
                return Response.error(ResultCode.EXAM_NOT_STARTED_ERROR);
            }
            if (currentDate.compareTo(endDate) > 0){
                return Response.error(ResultCode.EXAM_TIMEOUT_ERROR);
            }
        }catch (Exception e){
            return Response.error(ResultCode.DATE_FORMAT_ERROR.getMsg(), ResultCode.DATE_FORMAT_ERROR.getCode());
        }

        Map<Integer, List<IssueRecordInsertVo>> studentCache = tempCache.get(examInsertVo.getPaperId());
        if (studentCache == null) {
            studentCache = new HashMap<>();
        }

        List<IssueRecordInsertVo> preCache = studentCache.get(MyHandlerInterceptor.getUid());
        for (IssueRecordInsertVo studentAnswer : examInsertVo.getIssueAnswerVoList()) {
            //更新答题记录
            issueRecordService.addIssueRecordService(studentAnswer);

            if (!examIssueIdList.contains(studentAnswer.getIssueId())) {
                return Response.error(ResultCode.ISSUE_NOT_EXIST_ERROR);
            }

            int index = isContain(preCache, studentAnswer);
            if (preCache != null && index != ERROR) {
                Exam exam = examMapper.getExamByPaperIssueAndStudentId(examInsertVo.getPaperId(), studentAnswer.getIssueId(), MyHandlerInterceptor.getUid());
                exam.setAnswer(studentAnswer.getAnswer().toUpperCase());
                examMapper.updateById(exam);
                //更新缓存
                preCache.set(index, studentAnswer);
            } else {
                Exam exam = new Exam();
                BeanUtils.copyProperties(studentAnswer, exam);
                exam.setPaperId(examInsertVo.getPaperId());
                exam.setStudentId(MyHandlerInterceptor.getUid());

                int id = ERROR;
                examMapper.insert(exam);
                id = exam.getId();
                if (id == ERROR) {
                    return Response.error(ResultCode.INSERT_ERROR);
                }
            }
        }

        studentCache.put(MyHandlerInterceptor.getUid(), examInsertVo.getIssueAnswerVoList());
        tempCache.put(examInsertVo.getPaperId(), studentCache);

        ExamEvaluation evaluationCheck = examEvaluationMapper.getEvaluationByPaperAndUserId(examInsertVo.getPaperId(), MyHandlerInterceptor.getUid());
        if (evaluationCheck == null) {
            ExamEvaluation examEvaluation = new ExamEvaluation();
            examEvaluation.setPaperId(examInsertVo.getPaperId());
            examEvaluation.setStudentId(MyHandlerInterceptor.getUid());
            examEvaluationMapper.insert(examEvaluation);
        }

        return Response.ok();
    }

    private int isContain(List<IssueRecordInsertVo> issueRecordInsertVoList, IssueRecordInsertVo issueRecordInsertVo){
        if (issueRecordInsertVoList != null && issueRecordInsertVoList.size() != 0) {
            for (int i = 0; i < issueRecordInsertVoList.size(); i++) {
                if (issueRecordInsertVoList.get(i).getIssueId().equals(issueRecordInsertVo.getIssueId())) {
                    return i;
                }
            }
        }
        return ERROR;
    }

    private void failToEnterExamStudent(ExamPaper examPaper, List<Integer>examIssueIdList){
        String[] classes = examPaper.getClasses().split("\\$");

        for (String classId : classes){
            List<Integer> studentIds = classStudentMapper.getStudentIdByClassId(Integer.parseInt(classId));
            Set<Integer> enteredStudentIds = examMapper.getStudentIdById(examPaper.getId());
            List<Integer> notEnterStudentIds = studentIds.stream().filter(o->!enteredStudentIds.contains(o)).collect(Collectors.toList());
            for (Integer studentId : notEnterStudentIds){
                for (Integer examIssue : examIssueIdList){
                    Exam exam = new Exam();
                    exam.setPaperId(examPaper.getId());
                    exam.setAnswer("");
                    exam.setStudentId(studentId);
                    exam.setIssueId(examIssue);
                    examMapper.insert(exam);
                }
            }
        }

    }

    private void addExamEvaluation(ExamPaper examPaper){
        String[] classIds = examPaper.getClasses().split("\\$");
        for (String classId : classIds){
            List<Integer> studentIds = classStudentMapper.getStudentIdByClassId(Integer.parseInt(classId));
            for (Integer studentId : studentIds){
                ExamEvaluation check = examEvaluationMapper.getEvaluationByPaperAndUserId(examPaper.getId(), studentId);
                if (check == null) {
                    ExamEvaluation examEvaluation = new ExamEvaluation();
                    examEvaluation.setStudentId(studentId);
                    examEvaluation.setPaperId(examPaper.getId());
                    examEvaluationMapper.insert(examEvaluation);
                }
            }
        }
    }

    @Override
    public Response obtainExaminationPaper(Integer paperId) {
        ExamPaper examPaper = examPaperMapper.selectById(paperId);
        if (examPaper.getIsPublish() != 1) {
            return Response.error(ResultCode.EXAM_NOT_PUBLISH_ERROR);
        }

        try {
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
            Date startDate = simpleDateFormat.parse(simpleDateFormat.format(new Date(examPaperMapper.selectById(paperId).getStartTime().getTime())));
            Date endDate = simpleDateFormat.parse(simpleDateFormat.format(new Date(examPaperMapper.selectById(paperId).getEndTime().getTime())));
            Date currentDate = simpleDateFormat.parse(simpleDateFormat.format(new Date()));

            if (currentDate.compareTo(startDate) < 0){
                return Response.error(ResultCode.EXAM_NOT_STARTED_ERROR);
            }
            if (currentDate.compareTo(endDate) > 0 ) {
                return Response.error(ResultCode.EXAM_TIMEOUT_ERROR);
            }

        } catch (Exception e) {
            return Response.error(ResultCode.DATE_FORMAT_ERROR.getMsg(), ResultCode.DATE_FORMAT_ERROR.getCode());
        }

        ExamPaperSimpleMessage examPaperSimpleMessage = new ExamPaperSimpleMessage(examPaper);

        List<ExamIssue> result = examIssueMapper.getExamIssueByPaperId(paperId);
        if (result.size() == 0){
            return Response.error(ResultCode.EXAM_NOT_HAVE_ISSUE_ERROR);
        }

        List<Integer> ids = new ArrayList<>();
        for (ExamIssue examIssue : result) {
            ids.add(examIssue.getIssueId());
        }

        List<Issue> issueList = issueMapper.getIssuesByIds(ids);
        Map<Integer, List<Issue>> map = issueList.stream().collect(Collectors.groupingBy(
                Issue::getType));

        map = generateRandomIssues(map);

        Map<String, List<ExaminationIssueList>> listMap = new HashMap<>();

        for (Map.Entry<Integer, List<Issue>> entry : map.entrySet()) {
            Integer mapKey = entry.getKey();
            List<Issue> mapValue = entry.getValue();
            List<ExaminationIssueList> examIssueLists = new ArrayList<>();

            for (Issue issue : mapValue) {
                ExaminationIssueList examinationIssueList = new ExaminationIssueList(getExamIssue(result, issue.getId()));
                examinationIssueList.setIssueBriefMessage(new IssueBriefMessage(issue));
                examIssueLists.add(examinationIssueList);
            }

            listMap.put(IssueUtil.getIssueTypeName(mapKey), examIssueLists);
        }

        examPaperSimpleMessage.setListMap(listMap);
        return Response.ok(examPaperSimpleMessage);

    }

    @Override
    public void getExamPaperSimpleMessage(Integer paperId, int[] selectedIssueIds) {
        ExamPaper examPaper = examPaperMapper.selectById(paperId);
        ExamPaperSimpleMessage examPaperSimpleMessage = new ExamPaperSimpleMessage(examPaper);

        List<ExamIssue> result = examIssueMapper.getExamIssueByPaperId(paperId);
        List<Integer> ids = new ArrayList<>();
        for (ExamIssue examIssue : result) {
            ids.add(examIssue.getIssueId());
        }

        List<Integer> selectedIds = new ArrayList<>();
        for (int selectId : selectedIssueIds){
            if (ids.contains(selectId)){
                selectedIds.add(selectId);
            }
        }

        List<Issue> issueList = issueMapper.getIssuesByIds(selectedIds);
        Map<Integer, List<Issue>> map = issueList.stream().collect(Collectors.groupingBy(
                Issue::getType));

        map = sortByKey(map);

        Map<String, List<ExaminationIssueList>> listMap = new TreeMap<>();

        for (Map.Entry<Integer, List<Issue>> entry : map.entrySet()) {
            Integer mapKey = entry.getKey();
            List<Issue> mapValue = entry.getValue();
            List<ExaminationIssueList> examIssueLists = new ArrayList<>();

            for (Issue issue : mapValue) {
                ExaminationIssueList examinationIssueList = new ExaminationIssueList(getExamIssue(result, issue.getId()));
                examinationIssueList.setIssueBriefMessage(new IssueBriefMessage(issue));
                examIssueLists.add(examinationIssueList);
            }

            listMap.put(IssueUtil.getIssueTypeChineseName(mapKey), examIssueLists);
        }

        examPaperSimpleMessage.setListMap(listMap);

        PdfFontUtil.createExaminationPdf(examPaperSimpleMessage, response);
    }

    @Override
    public synchronized void finishExam(ExamPaper examPaper) {
        ExamPaper check = examPaperMapper.selectById(examPaper.getId());
        List<Integer> examIssueIdList = examIssueMapper.getIssueListByPaperId(examPaper.getId());
        failToEnterExamStudent(check, examIssueIdList);
        //防止提交后再次进入答题
        addExamEvaluation(check);
        //修改试卷状态
        examPaperMapper.finishExam(check.getId());
        //清理缓存
        tempCache.remove(check.getId());
    }

    /**
     * 让 Map按key进行排序
     */
    public <K extends Comparable<? super K>, V > Map<K, V> sortByKey(Map<K, V> map) {
        Map<K, V> result = new LinkedHashMap<>();

        map.entrySet().stream()
                .sorted(Map.Entry.<K, V>comparingByKey()).
                forEachOrdered(e -> result.put(e.getKey(), e.getValue()));
        return result;
    }

    private Map<Integer, List<Issue>> generateRandomIssues(Map<Integer, List<Issue>> map) {
        Map<Integer, List<Issue>> mapB = new HashMap<>();
        for (Map.Entry<Integer, List<Issue>> entry : map.entrySet()) {
            Integer mapKey = entry.getKey();
            List<Issue> mapValue = entry.getValue();
            Collections.shuffle(mapValue);
            mapB.put(mapKey, mapValue);
        }
        return mapB;
    }

    private ExamIssue getExamIssue(List<ExamIssue> examIssues, Integer issueId) {
        for (ExamIssue examIssue : examIssues) {
            if (examIssue.getIssueId().equals(issueId)) {
                return examIssue;
            }
        }
        return new ExamIssue();
    }

    @Override
    public synchronized Response recoveryExamIssue(Integer paperId) {
        ExamPaper check = examPaperMapper.selectById(paperId);
        if (check == null) {
            return Response.error(ResultCode.EXAM_EXIST_ERROR);
        }
        if (check.getIsPublish() == 0){
            return Response.error(ResultCode.EXAM_NOT_PUBLISH_ERROR);
        }
        if (check.getIsFinish() == 1){
            return Response.error(ResultCode.EXAM_TIMEOUT_ERROR);
        }
        if (check.getIsMark() == 1){
            return Response.error(ResultCode.GET_ERROR);
        }

        try {
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
            Date startDate = simpleDateFormat.parse(simpleDateFormat.format(check.getStartTime()));
            Date endDate = simpleDateFormat.parse(simpleDateFormat.format(check.getEndTime()));
            Date currentDate = simpleDateFormat.parse(simpleDateFormat.format(new Date()));

            if (currentDate.compareTo(startDate) < 0) {
                return Response.error(ResultCode.EXAM_NOT_STARTED_ERROR);
            }
            if (currentDate.compareTo(endDate) > 0){
                return Response.error(ResultCode.EXAM_TIMEOUT_ERROR);
            }
        }catch (Exception e){
            return Response.error(ResultCode.DATE_FORMAT_ERROR);
        }

        Map<Integer, List<IssueRecordInsertVo>> map = tempCache.get(paperId);
        List<IssueRecordInsertVo> recordList = new ArrayList<>();
        if (map != null) {
            recordList = tempCache.get(paperId).get(MyHandlerInterceptor.getUid());
        }else {
            List<Exam> examList = examMapper.getExamsByPaperIdAndStudentId(paperId, MyHandlerInterceptor.getUid());
            for (Exam exam : examList){
                recordList.add(new IssueRecordInsertVo(exam));
            }
        }

        return Response.ok(recordList);
    }

    @Override
    public Boolean checkAnswer(String realAnswer, String studentAnswer){
        String[] realAnswers = realAnswer.toUpperCase().split("\\$");
        String[] studentAnswers = studentAnswer.toUpperCase().split("\\$");

        if (realAnswers.length != studentAnswers.length){
            return false;
        }else {
            List<String> rightAnswer = Arrays.asList(realAnswers);
            boolean judge = true;
            for (String s:studentAnswers){
                if(!rightAnswer.contains(s)){
                    judge = false;
                    break;
                }
            }
            return judge;
        }
    }
}




